﻿/**
Library: transition managing library (TraM)
Author: Valentyn Derkach 2007.07.04
———————————————————————————————————————————————————————————————————————————————————————————————————————————————————
TweenProcessorInertialSpeeding performs tween processoring using inertial and speeding parameters.
The curSpeed is actually a change factor that is used for calculating the new value. curSpeed tends
to the dstSpeed and speedFactor is a change factor of this tending. Inertia is calculated as a 
multiplication of value offset and inertia factor. inertiaFade is a inertia fading factor.
In math idea is:
	changeFactor = (endChangeFactor - changeFactor)*speedFactor;
	offset = (endValue - currentValue)*changeFactor + inertia;
	inertia = offset*inertiaFactor + inertia*inertiaFadeFactor;
/**/

package com.bizmlib.tram
{
	
	
	public class EngineAccelerate extends Object implements IEngine
	{
		private static const PHASE_SPEEDING:int = 1;
		private static const PHASE_REVERSE:int = 2;

		public var currentAcceleration:Number = 0;
		//public var currentFadedAcceleration:Number = 0;
		public var initialAcceleration:Number = 0;
		public var finalAcceleration:Number = 0;
		public var accelerationChangeFactor:Number = 0;
		public var minLimit:Number = 1;
		public var maxLimit:Number = Number.POSITIVE_INFINITY;
		public var inertiaFactor:Number = 0;
		public var inertiaFade:Number = 0;
		public var fadeRanges:Object;
		private var inertiaValues:Object;
		private var maxDValue:Number = 0;
		private var phase:int;
		
		
		public function EngineAccelerate(initialAcceleration:Number, finalAcceleration:Number = undefined, accelerationChangeFactor:Number = undefined,
		minLimit:Number = undefined, maxLimit:Number = undefined, inertiaFactor:Number = 0, inertiaFade:Number = 0, fadeRanges:Object = null)
		{
			inertiaValues = new Object();
			
			if(isNaN(initialAcceleration))
			{
				throw new Error(this + " initialization failed: initialAcceleration is not a number");
			}
			else
			{
				this.initialAcceleration = currentAcceleration = initialAcceleration;
			}
			
			if(!isNaN(finalAcceleration) && isNaN(accelerationChangeFactor))
			{
				throw new Error(this + " initialization failed: accelerationChangeFactor is not a number though the finalAcceleration is, they both should be either undefined or a proper number values");
			}
			else if(isNaN(finalAcceleration) && !isNaN(accelerationChangeFactor))
			{
				throw new Error(this + " initialization failed: finalAcceleration is not a number though the accelerationChangeFactor is, they both should be either undefined or a proper number values");
			}
			else
			{
				this.finalAcceleration = isNaN(finalAcceleration) ? Number.NaN : finalAcceleration;
				this.accelerationChangeFactor = isNaN(accelerationChangeFactor) ? Number.NaN : accelerationChangeFactor;
			}
			
			this.minLimit = isNaN(minLimit) ? 0 : minLimit;
			this.maxLimit = isNaN(maxLimit) ? Number.POSITIVE_INFINITY : maxLimit;
			
			this.inertiaFactor = isNaN(inertiaFactor) ? 0 : inertiaFactor;
			this.inertiaFade = isNaN(inertiaFade) ? 0 : inertiaFade;
			
			this.fadeRanges = fadeRanges;
			
			phase = PHASE_SPEEDING;
		}

		public function reStart():void
		{
			phase = PHASE_SPEEDING;
			currentAcceleration = initialAcceleration;
			//currentFadedAcceleration = currentAcceleration;
			for(var propertyName:String in inertiaValues)
			{
				inertiaValues[propertyName] = 0;
			}
		}

		public function onBeforeIteration():void
		{
			maxDValue = 0;
			var finalAcceleration:Number = (phase==PHASE_REVERSE) ? this.initialAcceleration : this.finalAcceleration;
			if(!isNaN(finalAcceleration) && !isNaN(accelerationChangeFactor))
			{
				currentAcceleration += (finalAcceleration - currentAcceleration)*accelerationChangeFactor;
			}
			phase = PHASE_SPEEDING;
			//currentFadedAcceleration = currentAcceleration + (initialAcceleration - currentAcceleration)*accelerationChangeFactor;
		}

		public function performCalculation(propertyName:String, currentValue:Number, endValue:Number):Number
		{
			//trace("performCalculation(" + arguments + ")");
			var newValue:Number = currentValue, d:Number = 0;
			var fullDAbs:Number = Math.abs(currentValue - endValue);
			
			if(inertiaValues[propertyName]==undefined)
			{
				inertiaValues[propertyName]=0;
			}
			
			if(fullDAbs<minLimit && Math.abs(inertiaValues[propertyName])<minLimit)
			{
				newValue = endValue;
				inertiaValues[propertyName] = 0;
			}
			else
			{
				//var currentAcceleration:Number = (fullDAbs <= currentAcceleration) ? this.currentFadedAcceleration : this.currentAcceleration;
				d = (endValue - currentValue)*currentAcceleration + inertiaValues[propertyName];
				if(!isNaN(maxLimit))
				{
					if(Math.abs(d) > Math.abs(maxLimit))
					{
						d = ((d<0) ? -1 : 1)*Math.abs(maxLimit);
					}
				}
				newValue = currentValue + d;
				inertiaValues[propertyName] = inertiaValues[propertyName]*inertiaFade + d*inertiaFactor;
				
				var fadeRange:Number = (fadeRanges && !isNaN(fadeRanges[propertyName])) ? Math.abs(Number(fadeRanges[propertyName])) : 0;
				if(fadeRange > 0)
				{
					fullDAbs = Math.abs(newValue - endValue);
					if(fullDAbs <= fadeRange)
					{
						phase = PHASE_REVERSE;
					}
				}
			}
			
			maxDValue = Math.max(maxDValue, Math.abs(d));
			
			return newValue;
		}

		public function isFinal():Boolean
		{
			if(maxDValue == 0)
			{
				return true;
			}
			return false;
		}

		public function onAfterIteration():void
		{
		}


		public function toString():String
		{
			return "[EngineAccelerate]";
		}
	}
}